package com.ruoyi.kiku.service.utils;

import org.springframework.stereotype.Service;
import java.io.*;
import java.net.*;
import java.security.MessageDigest;
import java.util.*;
import java.util.concurrent.*;

@Service
public class DownloadService {

    private static final int BUFFER_SIZE = 8192;
    private static final int MAX_RETRY_COUNT = 3;
    private static final int CONNECT_TIMEOUT = 30_000;
    private static final int READ_TIMEOUT = 60_000;

    public byte[] downloadFileToMemory(String fileUrl, int threadCount) throws Exception {
        // 验证输入参数
        if (threadCount <= 0) {
            throw new IllegalArgumentException("线程数必须大于0");
        }

        URL url = new URL(fileUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("HEAD");
        connection.setConnectTimeout(CONNECT_TIMEOUT);
        connection.setReadTimeout(READ_TIMEOUT);

        // 获取文件大小
        long fileSize = connection.getContentLengthLong();
        if (fileSize <= 0) {
            throw new IOException("无法获取文件大小或文件大小为0");
        }

        System.out.println("开始下载: " + fileUrl);
        System.out.println("文件大小: " + fileSize + " bytes");
        System.out.println("线程数: " + threadCount);

        // 检查服务器是否支持断点续传
        boolean supportsRange = checkRangeSupport(fileUrl);
        if (!supportsRange || threadCount == 1) {
            System.out.println("服务器不支持断点续传或线程数为1，将使用单线程下载");
            return singleThreadDownloadToMemory(fileUrl);
        }

        // 调整线程数以避免过小的分块
        threadCount = adjustThreadCount(fileSize, threadCount);

        // 创建线程池和结果收集器
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        CompletionService<DownloadChunk> completionService = new ExecutorCompletionService<>(executor);

        // 计算每个线程下载的字节范围
        long chunkSize = fileSize / threadCount;
        List<Future<DownloadChunk>> futures = new ArrayList<>(threadCount);

        // 提交下载任务
        for (int i = 0; i < threadCount; i++) {
            long start = i * chunkSize;
            long end = (i == threadCount - 1) ? fileSize - 1 : start + chunkSize - 1;
            futures.add(completionService.submit(new MemoryDownloadTask(fileUrl, start, end)));
        }

        // 合并下载的数据块
        byte[] result = mergeDownloadedChunks(fileSize, threadCount, completionService, futures);

        executor.shutdown();
        return result;
    }

    private int adjustThreadCount(long fileSize, int threadCount) {
        // 确保每个分块至少有1MB大小
        long minChunkSize = 1024 * 1024; // 1MB
        if (fileSize / threadCount < minChunkSize) {
            int adjustedCount = (int) (fileSize / minChunkSize);
            if (adjustedCount <= 0) adjustedCount = 1;
            System.out.println("调整线程数从 " + threadCount + " 到 " + adjustedCount +
                    " 因为文件大小较小");
            return adjustedCount;
        }
        return threadCount;
    }

    private byte[] mergeDownloadedChunks(long fileSize, int threadCount,
                                         CompletionService<DownloadChunk> completionService,
                                         List<Future<DownloadChunk>> futures) throws Exception {

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream((int) fileSize);
        byte[][] chunks = new byte[threadCount][];
        int completed = 0;
        Set<Integer> completedIndices = new HashSet<>();

        while (completed < threadCount) {
            Future<DownloadChunk> future = completionService.take();
            DownloadChunk chunk = future.get();

            // 验证分块数据
            if (chunk.data == null || chunk.data.length == 0) {
                throw new IOException("下载的分块数据为空: " + chunk);
            }

            // 按顺序存储分块
            chunks[chunk.index] = chunk.data;
            completedIndices.add(chunk.index);
            completed++;

            System.out.printf("完成分块 %d/%d (%.1f%%) 大小: %d bytes\n",
                    completed, threadCount, (completed * 100.0 / threadCount), chunk.data.length);
        }

        // 按顺序合并所有分块
        for (int i = 0; i < threadCount; i++) {
            if (chunks[i] == null) {
                throw new IOException("缺失分块 " + i);
            }
            outputStream.write(chunks[i]);
        }

        byte[] mergedData = outputStream.toByteArray();

        // 验证合并后的文件大小
        if (mergedData.length != fileSize) {
            throw new IOException(String.format(
                    "文件大小不匹配! 预期: %d bytes, 实际: %d bytes",
                    fileSize, mergedData.length));
        }

        return mergedData;
    }

    public String detectContentType(String fileUrl) throws IOException {
        URL url = new URL(fileUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("HEAD");
        connection.setConnectTimeout(CONNECT_TIMEOUT);
        connection.setReadTimeout(READ_TIMEOUT);

        try {
            String contentType = connection.getContentType();
            return contentType != null ? contentType : "application/octet-stream";
        } finally {
            connection.disconnect();
        }
    }

    public String determineFileName(String fileUrl, String fileName) throws IOException {
        String contentType = detectContentType(fileUrl);
        String fileExtension = getFileExtension(contentType, fileUrl);

        if (fileName == null || fileName.isEmpty()) {
            // 从URL中提取文件名
            String path = new URL(fileUrl).getPath();
            int lastSlash = path.lastIndexOf('/');
            String urlFileName = lastSlash >= 0 ? path.substring(lastSlash + 1) : path;

            // 如果URL中没有文件名，使用默认名
            if (urlFileName.isEmpty()) {
                return "download" + fileExtension;
            }

            // 如果URL中的文件名已经有扩展名，直接使用
            if (urlFileName.contains(".")) {
                return urlFileName;
            }

            // 否则添加扩展名
            return urlFileName + fileExtension;
        }

        // 用户提供了文件名，确保有正确的扩展名
        if (!fileName.contains(".")) {
            return fileName + fileExtension;
        }

        return fileName;
    }

    public String calculateFileHash(byte[] data) throws Exception {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hash = digest.digest(data);
        return bytesToHex(hash);
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02x", b));
        }
        return result.toString();
    }

    private byte[] singleThreadDownloadToMemory(String fileUrl) throws IOException {
        HttpURLConnection connection = null;
        InputStream input = null;
        ByteArrayOutputStream output = new ByteArrayOutputStream();

        try {
            URL url = new URL(fileUrl);
            connection = (HttpURLConnection) url.openConnection();
            connection.setConnectTimeout(CONNECT_TIMEOUT);
            connection.setReadTimeout(READ_TIMEOUT);
            connection.connect();

            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                throw new IOException("服务器返回错误: " + connection.getResponseCode());
            }

            input = connection.getInputStream();
            byte[] buffer = new byte[BUFFER_SIZE];
            int bytesRead;
            long totalRead = 0;
            long lastLogTime = System.currentTimeMillis();

            while ((bytesRead = input.read(buffer)) != -1) {
                output.write(buffer, 0, bytesRead);
                totalRead += bytesRead;

                // 每5秒记录一次进度
                long currentTime = System.currentTimeMillis();
                if (currentTime - lastLogTime > 5000) {
                    System.out.printf("下载进度: %d bytes (%.1f%%)\n",
                            totalRead, (totalRead * 100.0 / connection.getContentLengthLong()));
                    lastLogTime = currentTime;
                }
            }

            return output.toByteArray();
        } finally {
            if (input != null) input.close();
            if (connection != null) connection.disconnect();
        }
    }

    private boolean checkRangeSupport(String fileUrl) throws IOException {
        HttpURLConnection connection = null;
        try {
            URL url = new URL(fileUrl);
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("HEAD");
            connection.setConnectTimeout(CONNECT_TIMEOUT);
            connection.setReadTimeout(READ_TIMEOUT);

            // 检查 Accept-Ranges 头
            String acceptRanges = connection.getHeaderField("Accept-Ranges");
            if (acceptRanges != null && acceptRanges.equalsIgnoreCase("bytes")) {
                return true;
            }

            // 如果 Accept-Ranges 头不存在，尝试 Range 请求
            connection.disconnect();
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setRequestProperty("Range", "bytes=0-1"); // 请求前2个字节
            connection.setConnectTimeout(CONNECT_TIMEOUT);
            connection.setReadTimeout(READ_TIMEOUT);

            int responseCode = connection.getResponseCode();
            return responseCode == HttpURLConnection.HTTP_PARTIAL;
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

    private String getFileExtension(String contentType, String fileUrl) {
        // 1. 首先尝试从URL中获取扩展名
        try {
            String path = new URL(fileUrl).getPath();
            if (path != null && path.contains(".")) {
                String urlExtension = path.substring(path.lastIndexOf('.'));
                // 简单验证扩展名格式 (如 .jpg, .mp4 等)
                if (urlExtension.matches("\\.[a-zA-Z0-9]{1,10}")) {
                    return urlExtension.toLowerCase();
                }
            }
        } catch (MalformedURLException e) {
            // URL格式错误，忽略继续下面的逻辑
        }

        // 2. 如果URL中没有有效扩展名，根据Content-Type判断
        if (contentType != null) {
            // 移除可能的字符集信息(如"text/html; charset=UTF-8")
            String mimeType = contentType.split(";")[0].trim().toLowerCase();

            // 常见MIME类型映射
            switch (mimeType) {
                // 文档类型
                case "application/pdf": return ".pdf";
                case "application/msword": return ".doc";
                case "application/vnd.openxmlformats-officedocument.wordprocessingml.document": return ".docx";
                case "application/vnd.ms-excel": return ".xls";
                case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": return ".xlsx";
                case "application/vnd.ms-powerpoint": return ".ppt";
                case "application/vnd.openxmlformats-officedocument.presentationml.presentation": return ".pptx";
                case "application/rtf": return ".rtf";
                case "text/plain": return ".txt";
                case "text/html": return ".html";
                case "text/css": return ".css";
                case "text/csv": return ".csv";
                case "application/json": return ".json";
                case "application/xml": return ".xml";

                // 图片类型
                case "image/jpeg": return ".jpg";
                case "image/png": return ".png";
                case "image/gif": return ".gif";
                case "image/webp": return ".webp";
                case "image/svg+xml": return ".svg";
                case "image/tiff": return ".tiff";
                case "image/bmp": return ".bmp";
                case "image/x-icon": return ".ico";

                // 音频类型
                case "audio/mpeg": return ".mp3";
                case "audio/ogg": return ".ogg";
                case "audio/wav": return ".wav";
                case "audio/aac": return ".aac";
                case "audio/webm": return ".weba";
                case "audio/x-wav": return ".wav";
                case "audio/x-m4a": return ".m4a";

                // 视频类型
                case "video/mp4": return ".mp4";
                case "video/ogg": return ".ogv";
                case "video/webm": return ".webm";
                case "video/x-msvideo": return ".avi";
                case "video/x-ms-wmv": return ".wmv";
                case "video/quicktime": return ".mov";

                // 压缩文件
                case "application/zip": return ".zip";
                case "application/x-rar-compressed": return ".rar";
                case "application/x-7z-compressed": return ".7z";
                case "application/x-tar": return ".tar";
                case "application/x-gzip": return ".gz";

                // 可执行文件
                case "application/x-msdownload": return ".exe";
                case "application/x-shockwave-flash": return ".swf";

                // 其他常见类型
                case "application/octet-stream": return ".bin";
                case "application/x-binary": return ".bin";

                // 按MIME类型前缀处理
                default:
                    if (mimeType.startsWith("text/")) {
                        return ".txt";
                    } else if (mimeType.startsWith("image/")) {
                        return ".img";
                    } else if (mimeType.startsWith("audio/")) {
                        return ".audio";
                    } else if (mimeType.startsWith("video/")) {
                        return ".video";
                    } else if (mimeType.startsWith("application/")) {
                        return ".dat";
                    }
            }
        }

        // 3. 默认扩展名
        return ".dat";
    }

    private static class DownloadChunk {
        final int index;
        final byte[] data;

        public DownloadChunk(int index, byte[] data) {
            this.index = index;
            this.data = data;
        }
    }

    private class MemoryDownloadTask implements Callable<DownloadChunk> {
        private final String fileUrl;
        private final long start;
        private final long end;
        private final int index;
        private int retryCount = 0;

        public MemoryDownloadTask(String fileUrl, long start, long end) {
            this.fileUrl = fileUrl;
            this.start = start;
            this.end = end;
            this.index = (int)(start / ((end - start) + 1)); // 计算分块索引
        }

        @Override
        public DownloadChunk call() throws Exception {
            while (retryCount < MAX_RETRY_COUNT) {
                HttpURLConnection connection = null;
                InputStream input = null;
                ByteArrayOutputStream output = new ByteArrayOutputStream();

                try {
                    URL url = new URL(fileUrl);
                    connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestProperty("Range", "bytes=" + start + "-" + end);
                    connection.setConnectTimeout(CONNECT_TIMEOUT);
                    connection.setReadTimeout(READ_TIMEOUT);

                    int responseCode = connection.getResponseCode();
                    if (responseCode != HttpURLConnection.HTTP_PARTIAL) {
                        throw new IOException("服务器不支持断点续传，响应码: " + responseCode);
                    }

                    input = connection.getInputStream();
                    byte[] buffer = new byte[BUFFER_SIZE];
                    int bytesRead;
                    long totalRead = 0;

                    while ((bytesRead = input.read(buffer)) != -1) {
                        output.write(buffer, 0, bytesRead);
                        totalRead += bytesRead;
                    }

                    // 验证下载的数据大小
                    long expectedSize = end - start + 1;
                    if (totalRead != expectedSize) {
                        throw new IOException(String.format(
                                "分块大小不匹配! 预期: %d bytes, 实际: %d bytes",
                                expectedSize, totalRead));
                    }

                    System.out.printf("线程 %d 下载完成: %d bytes (范围: %d-%d)\n",
                            Thread.currentThread().getId(), output.size(), start, end);

                    return new DownloadChunk(index, output.toByteArray());
                } catch (Exception e) {
                    retryCount++;
                    if (retryCount >= MAX_RETRY_COUNT) {
                        throw new IOException("下载分块失败，达到最大重试次数: " + retryCount, e);
                    }
                    System.out.printf("分块下载失败，第 %d 次重试... 错误: %s\n",
                            retryCount, e.getMessage());
                    Thread.sleep(1000 * retryCount); // 指数退避
                } finally {
                    if (input != null) input.close();
                    if (connection != null) connection.disconnect();
                }
            }
            throw new IOException("无法下载分块: " + start + "-" + end);
        }
    }
}
